use core::fmt::{self, Debug, Formatter};

use crate::config::MEMORY_END;
use crate::mm::address::PhysAddr;

use super::address::PhysPageNum;
use alloc::vec::Vec;
use spin::Mutex;
use lazy_static::*;


// 目前的物理内存上已经有一部分用于放置内核的代码和数据
// 我们需要将剩下可用的部分以单个物理页帧为单位管理起来
// 当需要存放应用数据或是应用的多级页表需要一个新节点的时候分配一个物理页帧 -> 通过 StackFrameAllocator 实现
// 在应用出错或退出的时候回收它占有的所有物理页帧 -> 通过 FrameTracker RAII 实现

trait FrameAllocator { // 这里的 Frame 指的是物理页帧
    fn new() -> Self;
    fn alloc(&mut self) -> Option<PhysPageNum>;
    fn dealloc(&mut self, ppn: PhysPageNum);
}

pub struct StackFrameAllocator { // 未被分配出去过的 物理页号区间 [current, end)
    current: usize,
    end: usize,
    recycled: Vec<usize>, // 以栈结构保存被回收的物理页号
}

impl StackFrameAllocator {
    pub fn init(&mut self, left: PhysPageNum, right: PhysPageNum) {
        self.current = left.0;
        self.end = right.0;
    }
}

impl FrameAllocator for StackFrameAllocator {
    fn new() -> Self {
        Self {
            current: 0,
            end: 0,
            recycled: Vec::new(),
        }
    }
    fn alloc(&mut self) -> Option<PhysPageNum> {
        // 先检查 recycled 中是否有之前回收的物理页号
        if let Some(ppn) = self.recycled.pop() {
            // 有则返回
            Some(ppn.into())
        } else {
            // 没有则在未被分配的物理页号区间[current, end)上分配
            if self.current == self.end {
                // 内存耗尽
                None
            } else {
                // 分配 current 出去
                self.current += 1;
                Some((self.current - 1).into())
            }
        }
    }
    fn dealloc(&mut self, ppn: PhysPageNum) {
        let ppn = ppn.0;
        // 检查 ppn 合法性
        // 条件:
        // 1. 该页面之前一定被分配出去过，因此它的物理页号一定 < current
        // 2. 该页面没有处在回收状态，即它的物理页号不能在栈 recycled 中找到
        if ppn >= self.current || 
                  self.recycled
                        .iter()
                        .find(|&v| { *v == ppn})
                        .is_some()
        {
            panic!("Frame ppn = {:#x} has not been allocated!", ppn);
        }
        // 回收
        self.recycled.push(ppn)
    }
}

lazy_static!{
    // 加锁的原因:
    // 在不触及 unsafe 的情况下实现 static mut 语义
    // 方便后续拓展到真正存在数据竞争风险的多核环境下运行
    pub static ref FRAME_ALLOCATOR: Mutex<StackFrameAllocator> = 
        Mutex::new(StackFrameAllocator::new());
}


// 用一个左闭右开的物理页号区间来表示 可用的物理内存
// 左端点是 ekernel 的物理地址以上取整方式转化成的物理页号
// 右端点是 MEMORY_END 以下取整方式转化成的物理页号。
pub fn init_frame_allocator() {
    extern "C" {
        fn ekernel(); // 内核代码结束地址 详见 file:///./../linker-qemu.ld
    }
    FRAME_ALLOCATOR
        .lock()
        .init(
             PhysAddr::from(ekernel as usize).ceil(),
            PhysAddr::from(MEMORY_END).floor()
        );
}

pub struct FrameTracker {
    pub ppn: PhysPageNum,
}

impl FrameTracker { // RAII 思想
    pub fn new(ppn: PhysPageNum) -> Self {
        let bytes_array = ppn.get_bytes_array();
        // 之前可能被分配过并用做其他用途
        // 将其空间初始化为 0 
        for i in bytes_array {
            *i = 0;
        }
        Self { ppn }
    }
}

impl Drop for FrameTracker {
    fn drop(&mut self) {
        frame_dealloc(self.ppn);
    }
}

impl Debug for FrameTracker {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("FrameTracker: PPN = {:#x}", self.ppn.0))
    }
}

pub fn frame_alloc() -> Option<FrameTracker> {
    FRAME_ALLOCATOR
        .lock()
        .alloc()
        .map(|ppn| FrameTracker::new(ppn))
}

fn frame_dealloc(ppn: PhysPageNum) {
    FRAME_ALLOCATOR
        .lock()
        .dealloc(ppn);
}

#[allow(unused)]
pub fn frame_allocator_test() {
    let mut v: Vec<FrameTracker> = Vec::new();

    for i in 0..5 {
        let frame = frame_alloc().unwrap();
        println!("{:?}", frame);
        v.push(frame);

    }

    v.clear();

    for i in 0..5 {
        let frame = frame_alloc().unwrap();
        println!("{:?}", frame);
        v.push(frame);
    }

    drop(v);

    println!("frame_allocator_test passed!");

}
